

import UIKit
import Foundation
import Security
import AdSupport
import AppTrackingTransparency
import SystemConfiguration.CaptiveNetwork
import Darwin

@objcMembers class FYDeviceUtil: NSObject {

    static func fy_getDeviceInfo() -> [String: String] {
        var deviceConfigMap: [String: String] = [:]

        deviceConfigMap["chess"] = UIDevice.current.name
        deviceConfigMap["litre"] = UIDevice.current.systemVersion
        deviceConfigMap["infant"] = String(Int(UIScreen.main.bounds.width))
        deviceConfigMap["creative"] = String(Int(UIScreen.main.bounds.height))
        deviceConfigMap["plural"] = String(ProcessInfo.processInfo.processorCount)
        deviceConfigMap["restless"] = FYDeviceUtil.fy_getIdfa()
        deviceConfigMap["bullet"] = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
        deviceConfigMap["scratch"] = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
        deviceConfigMap["compromise"] = UIDevice.current.identifierForVendor?.uuidString ?? ""
        deviceConfigMap["staff"] = FYModelTypeUtil.fy_getMobileDeviceModelType()
        deviceConfigMap["sequence"] = FYDeviceUtil.fy_getUuid()
        deviceConfigMap["wreck"] = FYPlatformUtil.fy_getScreenResolutionValue()
        deviceConfigMap["chimney"] = FYMemoryUtil.fy_getCashTotalSize()
        deviceConfigMap["liberate"] = FYMemoryUtil.fy_getRamTotalMemory()
        deviceConfigMap["petrol"] = FYMemoryUtil.fy_getCashAvailableSize()
        deviceConfigMap["minority"] = FYMemoryUtil.fy_getRamAvailableSize()
        deviceConfigMap["tragedy"] = String(FYPlatformUtil.fy_getBatteryLevel())
        deviceConfigMap["slope"] = String(fy_getUptimeWithoutResting())
        deviceConfigMap["cattle"] = FYPlatformUtil.fy_getTimeZone()
        deviceConfigMap["frontier"] = FYPlatformUtil.fy_getSystemLanguage()
        deviceConfigMap["miserable"] = String(fy_getUptimeWithResting())
        deviceConfigMap["symbol"] = FYDeviceUtil.fy_boolValueConvertToStringValue(fy_getDebuggerAttachedStatus())
        deviceConfigMap["civil"] = FYDeviceUtil.fy_boolValueConvertToStringValue(FYPlatformUtil.fy_getProxyStatus())
        deviceConfigMap["forecast"] = FYDeviceUtil.fy_boolValueConvertToStringValue(FYPlatformUtil.fy_getJailBreakedStatus())
        deviceConfigMap["eagle"] = FYDeviceUtil.fy_boolValueConvertToStringValue(FYPlatformUtil.fy_isSimulatorCheckStatus())
        deviceConfigMap["generally"] = FYDeviceUtil.fy_boolValueConvertToStringValue(FYPlatformUtil.fy_getChargingStatus())

        deviceConfigMap["reference"] = FYPlatformUtil.fy_getNetworkTypeConvertToValue()
        
        let wifiInfo = FYPlatformUtil.fy_getWifiInfo()
        let wifiSSID = wifiInfo?["ssid"] as? String ?? ""
        let wifiBSSID = wifiInfo?["bssid"] as? String ?? ""
        deviceConfigMap["fortunately"] = wifiSSID
        deviceConfigMap["carpet"] = wifiBSSID
        deviceConfigMap["volt"] = deviceConfigMap["fortunately"] ?? "null"
        
        for key in deviceConfigMap.keys {
            if deviceConfigMap[key] == "" {
                deviceConfigMap[key] = "null"
            }
        }

        deviceConfigMap["coarse"] = String(Int(FYPlatformUtil.fy_getScreenBrightnessValue()))
        deviceConfigMap["neutral"] = String(fy_getBootTime())
        deviceConfigMap["lodge"] = deviceConfigMap["carpet"] ?? "null"
        deviceConfigMap["volt"] = deviceConfigMap["fortunately"] ?? "null"
        deviceConfigMap["cargo"] = FYDeviceUtil.fy_boolValueConvertToStringValue(fy_getVPNConnectStatus())

        for key in deviceConfigMap.keys {
            deviceConfigMap[key] = "\(deviceConfigMap[key] ?? "null")"
        }

        return deviceConfigMap
    }
    
    
    static func fy_getUuid() -> String {
        if let uuidValue = fy_getUuidFromKeychain(service: "UuidKey"), !uuidValue.isEmpty {
            return uuidValue
        }

        let uuidString = UIDevice.current.identifierForVendor?.uuidString ?? UUID().uuidString

        let keychainQuery = fy_getKeychainQueryResult(service: "UuidKey")
        SecItemDelete(keychainQuery as CFDictionary)

        if let data = try? NSKeyedArchiver.archivedData(withRootObject: uuidString, requiringSecureCoding: false) {
            var addQuery = keychainQuery
            addQuery[kSecValueData as String] = data
            SecItemAdd(addQuery as CFDictionary, nil)
        }

        return uuidString
    }


    private static func fy_boolValueConvertToStringValue(_ b: Bool) -> String {
        return b ? "true" : "false"
    }

    static func fy_getKeychainQueryResult(service: String) -> [String: Any] {
        return [
            kSecAttrAccount as String: service,
            kSecAttrService as String: service,
            kSecClass as String: kSecClassGenericPassword
        ]
    }
    
    static func fy_getIdfa() -> String {
        if #available(iOS 14, *) {
            var idfaString = ""
            let semaphore = DispatchSemaphore(value: 0)
            ATTrackingManager.requestTrackingAuthorization { status in
                if status == .authorized {
                    idfaString = ASIdentifierManager.shared().advertisingIdentifier.uuidString
                }
                semaphore.signal()
            }
            semaphore.wait()
            return idfaString
        } else {
            return ASIdentifierManager.shared().advertisingIdentifier.uuidString
        }
    }
    
    static func fy_getUuidFromKeychain(service: String) -> String? {
        var query = fy_getKeychainQueryResult(service: service)
        query[kSecReturnData as String] = true
        query[kSecMatchLimit as String] = kSecMatchLimitOne

        var result: AnyObject?
        let status = SecItemCopyMatching(query as CFDictionary, &result)
        guard status == errSecSuccess, let data = result as? Data else {
            return nil
        }

        return try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? String
    }

    static func fy_getVPNConnectStatus() -> Bool {
        
        let version = UIDevice.current.systemVersion
        
        if Double(version) ?? 0 >= 9.0 {
            guard let dict = CFNetworkCopySystemProxySettings()?.takeRetainedValue() as? [String: Any],
                  let scoped = dict["__SCOPED__"] as? [String: Any] else {
                return false
            }
            for key in scoped.keys {
                if key.contains("tap") || key.contains("ppp") || key.contains("ipsec") || key.contains("tun") {
                    return true
                }
            }
        } else {
            var interfaces: UnsafeMutablePointer<ifaddrs>? = nil
            if getifaddrs(&interfaces) == 0 {
                var ptr = interfaces
                while ptr != nil {
                    let name = String(cString: ptr!.pointee.ifa_name)
                    if name.contains("tap") || name.contains("ppp") || name.contains("ipsec") || name.contains("tun") {
                        freeifaddrs(interfaces)
                        return true
                    }
                    ptr = ptr!.pointee.ifa_next
                }
                freeifaddrs(interfaces)
            }
        }
        return false
    }

    static func fy_getUptimeWithoutResting() -> Int64 {
        return Int64(ProcessInfo.processInfo.systemUptime * 1000)
    }

    static func fy_getUptimeWithResting() -> Int64 {
        var mib = [CTL_KERN, KERN_BOOTTIME]
        var boottime = timeval()
        var memorySize = MemoryLayout<timeval>.size
        var tz = timezone()
        var now = timeval()
        gettimeofday(&now, &tz)

        if sysctl(&mib, 2, &boottime, &memorySize, nil, 0) == 0 && boottime.tv_sec != 0 {
            let uptime = Int64(now.tv_sec - boottime.tv_sec) * 1000 + Int64(now.tv_usec - boottime.tv_usec) / 1000
            return uptime
        }
        return -1
    }

    static func fy_getDebuggerAttachedStatus() -> Bool {
        var info = kinfo_proc()
        var size = MemoryLayout<kinfo_proc>.stride
        var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()]
        let result = sysctl(&mib, UInt32(mib.count), &info, &size, nil, 0)
        if result != 0 {
            return false
        }
        
        return (info.kp_proc.p_flag & P_TRACED) != 0
    }

    static func fy_getBootTime() -> Int64 {
        let uptime = fy_getUptimeWithResting()
        let interval = Double(uptime) / 1000.0
        let date = Date(timeIntervalSinceNow: -interval)
        return Int64(date.timeIntervalSince1970 * 1000)
    }
}

